from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
import pandas as pd
import requests
import json
import time
import random
from scipy.stats import pearsonr


class CollaborativeFiltering(object):
    """
    协同过滤
    """
    @staticmethod
    def get_mean(arr):
        """
        一维数组，去掉空值/0值后的有效均值
        :param arr:
        :return:
        """
        return sum(arr) / sum([0 if num == 0 or num is np.nan else 1 for num in arr])

    def base_user(self):
        """
        基于用户
        :return:
        """
        user_a = [0, 4, 0, 3, 0, 4, 0]  # 用户a对不同产品评分
        user_b = [0, 3.5, 0, 2.5, 0, 4, 4]  # 用户b对不同产品评分
        user_c = [0, 4, 0, 3.5, 0, 4.5, 4.5]  # 用户c对不同产品评分

        # 去中心化（使得缺失值0标识中性）
        user_a = [num - self.get_mean(user_a) if num != 0 else 0 for num in user_a]
        user_b = [num - self.get_mean(user_b) if num != 0 else 0 for num in user_b]
        user_c = [num - self.get_mean(user_c) if num != 0 else 0 for num in user_c]

        b_similar_weight = cosine_similarity(np.array(user_a).reshape(1, -1),
                                              np.array(user_b).reshape(1, -1))
        c_similar_weight = cosine_similarity(np.array(user_a).reshape(1, -1),
                                             np.array(user_c).reshape(1, -1))
        print('b: {}; c: {}'.format(b_similar_weight, c_similar_weight))

        score = (b_similar_weight[0][0] * 4 + c_similar_weight[0][0] * 4.5) / \
                (b_similar_weight[0][0] + c_similar_weight[0][0])
        print('user_a的打分：{}'.format(score))

    def base_product(self):
        """
        基于产品
        :return:
        """
        p1 = [2, 0, 4, 0, 5]  # 不同用户对产品1的评分
        p2 = [0, 3, 0, 3, 0]  # 不同用户对产品2的评分
        p3 = [1, 0, 5, 0, 4]  # 不同用户对产品3的评分
        p4 = [0, 4, 4, 4, 0]  # 不同用户对产品4的评分
        p5 = [3, 0, 0, 0, 5]  # 不同用户对产品5的评分

        # 去中心化（使得缺失值0标识中性）
        product_1 = [num - self.get_mean(p1) if num != 0 else 0 for num in p1]
        product_2 = [num - self.get_mean(p2) if num != 0 else 0 for num in p2]
        product_3 = [num - self.get_mean(p3) if num != 0 else 0 for num in p3]
        product_4 = [num - self.get_mean(p4) if num != 0 else 0 for num in p4]
        product_5 = [num - self.get_mean(p5) if num != 0 else 0 for num in p5]

        # 计算余弦相似度
        for p in [product_1, product_2, product_3, product_4]:
            similar_weight = cosine_similarity(np.array(product_5).reshape(1, -1), np.array(p).reshape(1, -1))
            p.append(similar_weight[0][0])

        score = (product_1[-1] * p1[2] + product_2[-1] * p2[2] + product_3[-1] * p3[2] + product_4[-1] * p4[2]) / \
                (product_1[-1] + product_2[-1] + product_3[-1] + product_4[-1])
        print('第三个用户对p5的评分为：{}'.format(score))


# collaborative_filtering = CollaborativeFiltering()
# 基于用户
# collaborative_filtering.base_user()
# 基于产品
#  collaborative_filtering.base_product()


def collaborative_filtering(df, target_product, target_user, top_k=None, axis=1):
    """
    协同过滤
    :param df: 输出矩阵，缺失值未被0填充：索引为产品，列名为用户，值为评分
    :param target_product: 目标产品
    :param target_user: 目标用户
    :param top_k: 前k来计算余弦相似度，默认计算全部
    :param axis: 基于产品/用户，默认按行，基于产品
    :return:
    """
    def get_product(df_product):
        """
        基于产品的协同过滤
        :param df_product:
        :return:
        """
        csim_list = []
        for p in df.index:
            cos_score = cosine_similarity(np.nan_to_num(df_product.loc[target_product]).reshape(1, -1),
                                          np.nan_to_num(df_product.loc[p]).reshape(1, -1))
            csim_list.append(cos_score.item())

        df_product_new = pd.DataFrame({'similarity': csim_list, 'rating': df[target_user]}, index=df.index)

        df_top = df_product_new.dropna().copy()
        if top_k:
            df_top = df_top.sort_values('similarity', ascending=False)[: top_k]

        return (df_top['similarity'] * df_top['rating']).sum() / df_top['similarity'].sum()

    def get_user(df_user):
        """
        基于用户的协同过滤
        :param df_user:
        :return:
        """
        csim_list = []
        for user in df.columns:
            cos_score = cosine_similarity(np.nan_to_num(df_user[target_user]).reshape(1, -1),
                                          np.nan_to_num(df_user[user]).reshape(1, -1))
            csim_list.append(cos_score.item())

        df_user_new = pd.DataFrame([csim_list, df.loc[target_product]],
                                   columns=df.columns, index=['similarity', 'rating']).T

        df_top = df_user_new.dropna().copy()
        if top_k:
            df_top = df_top.sort_values('similarity', ascending=False)[: top_k]

        return (df_top['similarity'] * df_top['rating']).sum() / df_top['similarity'].sum()

    df_product = df.apply(lambda x: x - x.mean(), axis=1)
    df_user = df.apply(lambda x: x - x.mean())

    # product
    if axis == 1:
        return get_product(df_product)
    else:
        return get_user(df_user)


def collaborative_filtering_main():
    arr = [[2, np.nan, 4, np.nan, 5], [np.nan, 3, np.nan, 3, np.nan],
           [1, np.nan, 5, np.nan, 4], [np.nan, 4, 4, 4, np.nan],
           [3, np.nan, np.nan, np.nan, 5]]
    df = pd.DataFrame(arr, columns=['user1', 'user2', 'user3', 'user4', 'user5'])
    df.index = ['p1', 'p2', 'p3', 'p4', 'p5']
    print(df)

    product_result = collaborative_filtering(df=df, target_product='p5', target_user='user3')
    print(product_result)


class CollaborativeFilteringGithub(object):
    """
    github  推荐系统引擎
    基于github的start项目推荐
    """
    def __init__(self):
        self.myun = 'myun'
        self.mypw = 'mypw'
        self.first_url = 'https://api.github.com/user/starred'
        self.first_url_user = 'https://api.github.com/users/{}/starred'

    def get_url(self, url):
        """
        url请求
        :param url:
        :return:
        """
        sleep_time = random.randint(2, 10)

        print('sleep {}s'.format(sleep_time))
        time.sleep(sleep_time)
        req = requests.get(url, auth=(self.myun, self.mypw))
        return json.loads(req.text), req.links

    def get_github_url(self):
        """
        获取github所有个人信息
        :return:
        """
        resp_list = []
        req_json, req_link = self.get_url(url=self.first_url)
        resp_list.append(req_json)
        last_resp = req_link

        while last_resp.get('next'):
            next_url_to_get = last_resp['next']['url']
            next_req_json, next_req_link = self.get_url(url=next_url_to_get)
            last_resp = next_req_link
            resp_list.append(next_req_json)
        return resp_list

    def get_started_by_me(self):
        """
        获取start的url
        :return:
        """
        # 获取start的url
        my_started_repos = []
        for info in self.get_github_url():
            for data in info:
                my_started_repos.append(data['html_url'])

        # 获取所有start项目的作者
        if my_started_repos:
            users = []
            for data in my_started_repos:
                right_split = data.split('.com/')[1]
                start_user = right_split.split('/')[0]
                users.append(start_user)

            return users, my_started_repos

    def get_started_by_user(self, user_name):
        """
        获取某用户的所有start项目
        :param user_name:
        :return:
        """
        # 获取所有项目
        started_resp_list = []
        first_url = self.first_url_user.format(user_name)
        req_json, req_link = self.get_url(url=first_url)
        last_resp = req_link
        started_resp_list.append(req_json)

        while last_resp.get('next'):
            next_url = last_resp['next']['url']
            next_req_json, last_resp = self.get_url(url=next_url)
            started_resp_list.append(next_req_json)

        # 获取所有项目的url
        start_url = []
        for project in started_resp_list:
            for info in project:
                start_url.append(info['html_url'])

    def get_unstart_project(self, starred_repos, repo_vocab, my_started_repos):
        """
        通过对所有作者对所有项目的是否start构建矩阵，加入自己的start项目，运用皮尔逊相关系数（中心化余弦相似度）来计算
        与自己最相似的用户排名

        :param starred_repos: 我start的项目的作者的全部project的url
        :param repo_vocab: 为所有加标的资料库，构建一个特征集（我start的所有作者的所有项目）
        :param my_started_repos: 我start的所有项目
        :return: 与我相似度倒叙的作者/用户排名
        """
        # 构建二维向量，若用户对该库start，则为1，否则为0
        all_user_vector = []
        for k, v in starred_repos.items():
            user_vector = []
            for url in list(set(repo_vocab)):
                is_started = 1 if url in v else 0
                user_vector.append(is_started)
            all_user_vector.append(user_vector)

        # index为用户，column为项目，值为是否start
        df = pd.DataFrame(all_user_vector, columns=list(set(repo_vocab)), index=starred_repos.keys())
        # 添加自己的start
        my_repo = []
        for project in df.columns:
            is_started = 1 if project in my_started_repos else 0
            my_repo.append(is_started)
        mrc = pd.Series(my_repo).to_frame('fish-work').T
        mrc.columns = df.columns

        df = pd.concat([df, mrc])

        #  用自己的与所有用户进行相似度计算
        #  Pearson相关系数：中心化余弦相似度
        sim_score = {}
        for num in range(len(df)):
            ss = pearsonr(df.iloc[-1], df.iloc[num])
            sim_score.update({num: ss[0]})

        sf = pd.Series(sim_score).to_frame('similarity')
        #  按相似度倒叙排名
        return sf.sort_values('similarity', ascending=False)

    def main(self):
        """
        程序主入口
        :return:
        """
        # 获取全部start项目作者,以及我start的项目url
        my_started_users, my_started_repos = self.get_started_by_me()
        print(my_started_users)

        #  获取作者全部project的url
        starred_repos = {k: self.get_started_by_user(user_name=k) for k in set(my_started_users)}
        print(starred_repos)

        # 为所有加标的资料库，构建一个特征集
        repo_vocab = [item for s1 in list(starred_repos.values()) for item in s1]

        # 获取与自己匹配到倒叙的所有项目及相似度
        interest_project = self.get_unstart_project(starred_repos, repo_vocab, my_started_repos)
        print(interest_project)
